﻿#!/usr/bin/env python
# -*- coding: utf-8 -*-

# ares/hexviewer.py

# Copyright 2009-2010 Diego Hansen Hahn (aka DiegoHH) <diegohh90 [at] hotmail [dot] com>

# Bless Hex Editor
# Copyright 2008 Alexandros Frantzis

# lazynds is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License.

# lazynds is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with lazynds. If not, see <http://www.gnu.org/licenses/>.

''' Livremente baseado no Bless Hex Editor '''

import gtk
import pango

import os

fontDescription = 'Courier 12'
width = 10
height = 20

class HexDrawer:

    def __init__(self):
        self.hex_charset = "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF"

    def create(self, widget, pango_context, fg, bg = gtk.gdk.Color(0xFFFF, 0xFFFF, 0xFFFF)):
        self.win = widget.window
        self.gc = gtk.gdk.GC(self.win)
        # Desenha o background (no caso, será sempre branco)
        self.gc.set_rgb_fg_color(bg)

        # Criando o desenho da fonte hexadecimal
        self.pixmap = gtk.gdk.Pixmap(self.win, len(self.hex_charset)*width, height, -1)
        self.pixmap.draw_rectangle(self.gc, True, 0, 0, len(self.hex_charset)*width, height)

        # Renderizando a fonte
        pango_layout = pango.Layout(pango_context)
        pango_layout.set_text(self.hex_charset)

        self.gc.set_rgb_fg_color(fg)
        self.pixmap.draw_layout(self.gc, 0, 0, pango_layout)

    def draw(self, byte, x, y):
        self.win.draw_drawable(self.gc, self.pixmap, byte*width*2, 0, x, y, width*2, height)

class AsciiDrawer:

    def __init__(self):
        self.ascii_charset = '................................ !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~.................................................................................................................................'

    def create(self, widget, pango_context, fg, bg = gtk.gdk.Color(0xFFFF, 0xFFFF, 0xFFFF)):
        self.win = widget.window
        self.gc = gtk.gdk.GC(self.win)
        # Desenha o background (no caso, será sempre branco)
        self.gc.set_rgb_fg_color(bg)

        # Criando o desenho da fonte ascii
        self.pixmap = gtk.gdk.Pixmap(self.win, len(self.ascii_charset)*width, height, -1)
        self.pixmap.draw_rectangle(self.gc, True, 0, 0, len(self.ascii_charset)*width, height)

        # Renderizando a fonte
        pango_layout = pango.Layout(pango_context)
        pango_layout.set_text(self.ascii_charset)

        self.gc.set_rgb_fg_color(fg)
        self.pixmap.draw_layout(self.gc, 0, 0, pango_layout)

    def draw(self, byte, x, y):
        self.win.draw_drawable(self.gc, self.pixmap, byte*width, 0, x, y, width, height)

class OffsetDrawer:

    def __init__(self):
        self.off_charset = "0123456789ABCDEF"

    def create(self, widget, pango_context, fg, bg = gtk.gdk.Color(0xFFFF, 0xFFFF, 0xFFFF)):
        self.win = widget.window
        self.gc = gtk.gdk.GC(self.win)
        # Desenha o background (no caso, será sempre branco)
        self.gc.set_rgb_fg_color(bg)

        # Criando o desenho da fonte hexadecimal
        self.pixmap = gtk.gdk.Pixmap(self.win, len(self.off_charset)*width, height, -1)
        self.pixmap.draw_rectangle(self.gc, True, 0, 0, len(self.off_charset)*width, height)

        # Renderizando a fonte
        pango_layout = pango.Layout(pango_context)
        pango_layout.set_text(self.off_charset)

        self.gc.set_rgb_fg_color(fg)
        self.pixmap.draw_layout(self.gc, 0, 0, pango_layout)

    def draw(self, byte, x, y):
        self.win.draw_drawable(self.gc, self.pixmap, byte*width, 0, x, y, width, height)


class HexViewer(gtk.DrawingArea):

    __gsignals__ = {'realize' : 'override',
            'expose-event' : 'override'}


    def __init__(self):
        gtk.DrawingArea.__init__(self)

        self.set_size_request(665,490)
        self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('white'))

        self.hexData = ""
        self.hexLen = 0
        self.hexStart = 0


    def initialize_pixmaps(self):
        pango_context = self.create_pango_context()
        pango_context.set_font_description(pango.FontDescription(fontDescription))

        self.evenHex = HexDrawer()
        self.evenHex.create(self, pango_context, gtk.gdk.Color(0,0,0x3333))

        self.oddHex = HexDrawer()
        self.oddHex.create(self, pango_context, gtk.gdk.Color(0,0,0xFFFF))

        self.Ascii = AsciiDrawer()
        self.Ascii.create(self, pango_context, gtk.gdk.Color(0,0,0))

        self.Offset = OffsetDrawer()
        self.Offset.create(self, pango_context, gtk.gdk.Color(0,0,0))

    def set_data(self, data, start):
        self.hexData = data[0]
        self.hexLen = data[1]
        self.hexStart = start

    def set_start(self, start):
        self.hexStart = start

    def draw_lines(self, width, height):
        self.gc = gtk.gdk.GC(self.window)
        self.gc.set_rgb_fg_color(gtk.gdk.Color(0xBBBB,0xBBBB,0xBBBB))
        self.window.draw_line(self.gc, 0, 20, width, 20)
        self.window.draw_line(self.gc, 90, 0, 90, height)
        self.window.draw_line(self.gc, 495, 0, 495, height)

    def draw_buffer(self):
        win_width, win_height = self.window.get_size()

        self.draw_lines(win_width, win_height)

        posy = 0
        posx_hex = 95
        posx_ascii = 500
        for x in range(16):
            string = '%02X' % x
            for y in string:
                byte = self.Offset.off_charset.index(y)
                self.Offset.draw(byte, posx_hex, posy)
                posx_hex += width
            string = '%01X' % x
            for y in string:
                byte = self.Offset.off_charset.index(y)
                self.Offset.draw(byte, posx_ascii, posy)
            posx_hex += 5
            posx_ascii += width


        posy = height + 5
        posx_off = 5
        # Preenche com os offsets:
        start = self.hexStart*16
        while True:
            string = '%08X' % start
            for x in string:
                byte = self.Offset.off_charset.index(x)
                self.Offset.draw(byte, posx_off, posy)
                posx_off += width
            start += 0x10
            posy += height
            posx_off = 5
            if posy > (win_height - height):
                break
            if start > self.hexLen-1:
                break

        posy = height + 5
        posx_hex = 95
        posx_ascii = 500
        i = False

        if isinstance(self.hexData, str):
            for x in self.hexData[self.hexStart*16:]:
                byte = ord(x)
                if not i:
                    self.oddHex.draw(byte, posx_hex, posy)
                    i = True
                else:
                    self.evenHex.draw(byte, posx_hex, posy)
                    i = False
                self.Ascii.draw(byte, posx_ascii, posy)
                posx_hex += (2*width + 5)
                posx_ascii += width
                if posx_hex == 95 + (2*width + 5)*16:
                    posy += height
                    posx_hex = 95
                    posx_ascii = 500
                if posy > (win_height - height):
                    break
        else:
            self.hexData.seek(self.hexStart*16, 0)
            while True:
                byte = self.hexData.read(1)
                if not byte:
                    break
                byte = ord(byte)
                if not i:
                    self.oddHex.draw(byte, posx_hex, posy)
                    i = True
                else:
                    self.evenHex.draw(byte, posx_hex, posy)
                    i = False
                self.Ascii.draw(byte, posx_ascii, posy)
                posx_hex += (2*width + 5)
                posx_ascii += width
                if posx_hex == 95 + (2*width + 5)*16:
                    posy += height
                    posx_hex = 95
                    posx_ascii = 500
                if posy > (win_height - height):
                    break


    def do_expose_event(self, event):
        self.draw_buffer()


    def do_realize(self):
        self.set_flags(self.flags() | gtk.REALIZED)

        events = gtk.gdk.EXPOSURE_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.POINTER_MOTION_MASK

        self.window = gtk.gdk.Window(self.get_parent_window(),
                         x = self.allocation.x,
                         y = self.allocation.y,
                         width = self.allocation.width,
                         height = self.allocation.height,
                         window_type = gtk.gdk.WINDOW_CHILD,
                         wclass = gtk.gdk.INPUT_OUTPUT,
                         visual = self.get_visual(),
                         colormap = self.get_colormap(),
                         event_mask = self.get_events() | events)
        self.window.set_user_data(self)
        self.style.attach(self.window)
        self.style.set_background(self.window, gtk.STATE_NORMAL)

    def refresh(self):
        alloc = self.get_allocation()
        rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height)
        self.window.invalidate_rect(rect, True)
        self.window.process_updates(True)
        return False